/*日志落地模块的实现
1.抽象落地基类
2.派生子类（根据不同的落地方向进行派生）
3.使用工厂模式进行创建与表示分离
*/
#ifndef __M_SINK_H__
#define __M_SINK_H__
#include "util.hpp"
#include <fstream>
#include <sstream>
#include <memory>
#include <cassert>
#include <unistd.h>
namespace logslearn
{
    // 抽象落地基类
    class LogSink
    {
    public:
        using ptr = std::shared_ptr<LogSink>;
        LogSink() {}
        virtual ~LogSink() {}
        // 纯虚函数,日志落地功能
        virtual void log(const char *data, size_t len) = 0;
    };
    // 落地方向：标准输出
    class StdoutSink : public LogSink
    {
    public:
        // 将日志消息写入到标准输出
        void log(const char *data, size_t len)
        {
            std::cout.write(data, len); // 因为日志输出不一定是字符串，所以不能直接打印，因此需要调用write接口,从data位置开始写，写入len长度的数据
        }
    };
    // 落地方向：指定文件
    class FileSink : public LogSink
    {
    public:
        // 构造时存入文件名，并打开文件，将操作句柄管理起来
        FileSink(const std::string &pathname) : _pathname(pathname)
        {
            // 1.创建日志文件所在的目录,没有文件就创建文件
            logsLearn::util::File::createDirectory(logsLearn::util::File::path(pathname));
            // 2.按特殊方式打开文件
            _ofs.open(_pathname, std::ios::binary | std::ios::app); // 二进制可写可追加权限
            assert(_ofs.is_open());
        }
        // 将日志消息写入到标准输出
        void log(const char *data, size_t len)
        {
            _ofs.write(data, len);
            assert(_ofs.good()); // 打开失败就报错
        }

    private:
        std::string _pathname;
        std::ofstream _ofs; // 会默认以写的方式打开文件
    };
    // 落地方向：滚动文件（以大小进行滚动）
    class RoolBySizeSink : public LogSink
    {
    public:
        // 构造时存入文件名，并打开文件，将操作句柄管理起来// 需要用户告知，基础的文件名和文件大小
        RoolBySizeSink(const std::string &basename, size_t max_fsize) :_basename(basename), _max_fsize(max_fsize), _cur_fsize(0),_name_count(0){
            std::string pathname=createNewFile();
            // 1.创建日志文件所在的目录,没有文件就创建文件
            logsLearn::util::File::createDirectory(logsLearn::util::File::path(pathname));
            // 2.按特殊方式打开文件
            _ofs.open(pathname, std::ios::binary | std::ios::app); //打开文件 二进制可写可追加权限
            assert(_ofs.is_open());
        } 
        // 将日志消息写入到标准输出--写入前判断文件大小，超过了最大大小就要切换文件
        void log(const char *data, size_t len){
            if(_cur_fsize>=_max_fsize){
                _ofs.close();//打开文件，就必须关闭文件（这里关闭以前的文件）
                std::string pathname =createNewFile();//创建新文件
                 _ofs.open(pathname, std::ios::binary | std::ios::app); //打开文件 二进制可写可追加权限
                 assert(_ofs.is_open());//打开失败就报错
                 _cur_fsize=0;
            }
            _ofs.write(data,len);
            assert(_ofs.good());//检测文件流状态和文件读写过程是否正常   
            _cur_fsize+=len;
        }
        // 创建一个新文件，不需要用户去创建，所有我们把权限设置为私有
        std::string createNewFile(){
            //获取系统时间，以时间来构造文件名的扩展名
            time_t t=logsLearn::util::Data::now();
            struct tm lt;
            localtime_r(&t,&lt);
            std::stringstream filename;
            filename<<_basename;
            filename<<lt.tm_year+1900;
            filename<<lt.tm_mon+1;
            filename<<lt.tm_mday;
            filename<<lt.tm_hour;
            filename<<lt.tm_min;
            filename<<lt.tm_sec;
            filename<<"-";
            filename<<_name_count++;
            filename<<".log";
            return filename.str();
        }; // 进行大小判断，超过指定大小则需要创建新文件,将一个时间戳，转化为时间结构
    private:
        // 通过基础文件名+扩展文件名（以时间生成）组成一个实际的当前输出文件名
        size_t _name_count;//名称计数器
        std::string _basename; // 文件的基础名字如./logs/base-    ./logs/base-20240710.log
        std::ofstream _ofs;
        size_t _max_fsize; // 最大文件大小，当前文件超过了这个大小就要切换文件
        size_t _cur_fsize; // 记录当前文件已经写入的数据大小
    };
    // 简单工厂模式，进行生成管理
    //SinkType通过模板参数，可以生产我们需要的落地方式，因为落地方式需要传参的参数不一样，这里我们需要用到不定参的知识   
    class SinkFactory
    {
        public:
        template<typename SinkType,typename ...Args>
        static LogSink::ptr create(Args && ...args){
            return std::make_shared<SinkType>(std::forward<Args>(args)...);
        }
    };
}
#endif